package Assembler;

import Common.*;
//import Common.Instruction;
//import Common.Errors;
//import Common.Utilities;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

public class AssemblerPassOne
{

    List<IntermediateData> Data;
    Table symTable;
    //Table litTable;
    int lc;
    int lineNum;
    boolean fatalError = false;

    public AssemblerPassOne(String file)
    {
        Data = new ArrayList<IntermediateData>();
        symTable = new Table();
        try
        {
            BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
            lc = 0;
            lineNum = 0;
            //while (in.ready())
            while (in.ready() && !fatalError)
            {
                if (lc <= 1023)
                {
                    Data.add(ParseLine(in.readLine()));
                } else
                {
                    //line count error
                    System.out.println("InvalidLineCount: " + Errors.GetErrorMessage("InvalidLineCount"));
                    break;
                }
            }
        } catch (FileNotFoundException ex)
        {
            // Error handling here
            ex.printStackTrace();
        } catch (IOException ex)
        {
            // Error handling here
            ex.printStackTrace();
        }
    }

    private IntermediateData ParseLine(String line)
    {
        // Create a new data object and set the source line
        IntermediateData result = new IntermediateData(line, ++lineNum);
        int commentIndex = line.indexOf(":");
        if (commentIndex > 3)
        {
            if (line.charAt(commentIndex - 1) == '=' || line.charAt(commentIndex - 2) == '=')
            {
                commentIndex = line.indexOf(":", commentIndex + 1);
            }
        }
        if (commentIndex > -1)  // If there is a comment in the line, parse it and remove it
        {
            result.Comment = line.substring(commentIndex, line.length());
            line = line.substring(0, commentIndex);
            if (commentIndex == 0) // if the line begins with a comment, then the entire line is a comment
            {
                return result; // Comment at start. No other commands
            }
        }

        String[] columns = line.split("[ \t]+", 3);    // split based on any combination of tabs

        if (columns.length > 2)
        {
            int temp = 3;
            if (columns[2].indexOf(" ") > -1)
            {
                temp = 4;
            }
            if (line.split("[ \t]+").length > temp)
            {
                result.Errors.add("InvalidComment");
            }
            if (!columns[0].equals(""))
            {
                result.Label = columns[0];
                if (Utilities.ValidateLabel(result.Label))
                {
                    //System.out.println("Found label: " + result.Label);
                    if (symTable.Rows.containsKey(result.Label))
                    {
                        result.Errors.add("InvalidDuplicateLabel");
                    }
                    else
                    {
                        symTable.Rows.put(result.Label, new SymbolRow(result));       // it has a label so add it to the symbol table
                    }
                }
                else
                {
                    //System.out.println("Found invalid label on line " + lineNum);
                    result.Errors.add("InvalidLabel");
                }
            }
            result.LC = lc;

            ParseOperator(result, columns);

            result.ComputeAbsolute();
        }
        else if (columns.length > 1)
        {
            if (columns[1].equalsIgnoreCase("NOP"))
            {
                result.Directive = Instruction.GetDirective("NOP");
                if (!columns[0].equals(""))
                {
                    result.Errors.add("InvalidNullLabel");
                }
                result.LC = lc;
                lc++;
            }
        }
        else    //a blank line is found
        {
            result.Errors.add("InvalidBlankLine");
        }
        return result;
    }

    /**
     *
     * @param result the partially built IntermediateData object to be finished
     * @param columns the array containing the portions of the line
     * @param index current index
     * @return new index
     */
    private void ParseOperator(IntermediateData result, String[] columns)
    {
        String operator = columns[1].trim().toUpperCase();
        result.Type = Instruction.GetType(operator);
        if (result.Type == Instruction.Type.OPERATOR)
        {
            result.Operands = columns[2].trim().split(",");
            result.Operator = Instruction.GetOperator(operator);

            //cutoff from use operand invalid label if one is present
            if (result.Operands.length > 1)
            {
                result.Operands[1] = result.Operands[1].trim().split("[ \t]+")[0]; 
            }
            if (result.Operator.Functions.get(result.Operands[0].toUpperCase()) != null)
            {
                result.Function = result.Operator.Functions.get(result.Operands[0].toUpperCase());
            }
            else
            {
                result.Errors.add("InvalidFunction");
            }
             
            for (int i = 1; i < result.Operands.length; i++)
            {
                if (result.Operands[i].compareTo("") == 0)
                {
                    result.Operator = Instruction.GetOperator("MOPER");
                    result.Function = Instruction.GetOperator("MOPER").Functions.get("ADD");
                    String[] new_op = {"ADD","0"};
                    result.Operands = new_op;
                    result.Errors.add("InvalidCommaCount");
                    break;
                }
            }
            if (result.Function != null)
            {
                if (result.Operands.length > 1)
                {

                    String operandError = null;
                    if ((result.Operator.Format & Instruction.Format.FUNCTION) > 0 )
                    {
                        operandError = CheckOperandError(result.Operands[1], result.Function.Operand_Format);

                    }
                    else
                    {
                        operandError = CheckOperandError(result.Operands[1], result.Operator.Format);
                    }
                    if (operandError != null)
                    {
                        result.Errors.add(operandError);
                    }
                }
                else if ((result.Function.Operand_Format & Instruction.Format.NONE) == 0)
                {
                    result.Errors.add("InvalidSField");
                }
            }

            lc++;
        }
        else if (result.Type == Instruction.Type.DIRECTIVE)
        {
            result.Directive = Instruction.GetDirective(operator);
            if (columns.length > 2)  // If there is a next term
            {
                if (!columns[2].startsWith(":"))    // If the next term is not a comment
                {
                    result.Operands = new String[1];
                    result.Operands[0] = columns[2].trim();
                }
            }
            String operandError = CheckOperandError(result.Operands[0], result.Directive.Operand_Format);
            String labelError = CheckOperandError(result.Label, result.Directive.Label_Format);

            if (labelError != null)
            {
                result.Errors.add(labelError);
            }
            if (operandError != null)
            {
                result.Errors.add(operandError);
            }

//            if (result.Directive == Instruction.GetDirective("DAT"))
//            {
//                System.out.println("Testing literal");
//                if (!Utilities.ValidateLiteral(result.Operands[0]))
//                {
//                    //result.Operands[0] = "0";
//                    System.out.println("Found error");
//                    result.Errors.add(("InvalidDatVal"));
//                }
//            }
//            else if(result.Directive == Instruction.GetDirective("START")
//                    || result.Directive == Instruction.GetDirective("RESET")
//                    || result.Directive == Instruction.GetDirective("EQU")
//                    || result.Directive == Instruction.GetDirective("EQUE"))
//            {
//
//                if (result.Directive == Instruction.GetDirective("START"))
//                {
//                    lc = Integer.parseInt(result.Operands[0]);
//                    //validate range of the directive
//
//                }
            if (result.Directive == Instruction.GetDirective("EQU"))
            {

                String operand = result.Operands[0];
                System.out.println("Checking operand " + operand);
                while(symTable.Rows.containsKey(operand))
                {
                    System.out.println("Found " + operand);

                    if (symTable.Rows.get(operand).Data.EquValue != null)
                    {
                        operand = symTable.Rows.get(operand).Data.EquValue;
                        System.out.println("Operand now " + operand);
                        if (operand.equals(result.Label))
                        {
                            result.Errors.add("InvalidEQULabel");
                            break;
                        }
                    }
                    else
                    {
                        System.out.println("No Record of " + operand);
                        break;
                    }
                }
                System.out.println("EQU value is " + operand);
                result.EquValue = operand;

            }
            else if(result.Directive == Instruction.GetDirective("EQUE"))
            {
                int equResult = 0;
                String expression = result.Operands[0];
                String stringPart;
                int indexofPlus = expression.indexOf('+');
                int indexofMinus = expression.indexOf('-');
                int next = 1;
                int current = 1;
                int operatorCount = 0;
                int temp = -1;
                while (indexofPlus > 0 || indexofMinus > 0)
                {

                    if (indexofMinus == -1 || (indexofPlus != -1 && indexofPlus < indexofMinus) ) // next is plus
                    {
                        System.out.println("- index = " + indexofMinus);
                        System.out.println("+ index = " + indexofPlus);
                        stringPart = expression.substring(0, indexofPlus);
                        expression = expression.substring(indexofPlus+1);
                        next = 1;
                    }
                    else
                    {
                        stringPart = expression.substring(0, indexofMinus);
                        expression = expression.substring(indexofMinus+1);
                        next = -1;
                    }

                    if (operatorCount == 0 && stringPart.equals("*"))
                    {
                        equResult += lc;
                    }
                    else
                    {
                        temp = EvaluateEQUeLabel(stringPart);
                        if(temp != -1)
                        {
                            equResult += current*temp;
                        }
                        else
                        {
                            System.out.println("Found error with equ");
                            result.Errors.add("InvalidEQULabel");
                        }
                    }
                    indexofPlus = expression.indexOf('+');
                    indexofMinus = expression.indexOf('-');
                    current = next;
                    operatorCount++;
                }
                if (operatorCount == 0 && expression.equals("*"))
                {
                        equResult += lc;
                }
                else
                {
                    temp = EvaluateEQUeLabel(expression);
                    if(temp != -1)
                    {
                        equResult += current*temp;
                    }
                    else
                    {
                        System.out.println("Found error with equ");
                        result.Errors.add("InvalidEQULabel");
                    }
                }
                result.EquValue = String.valueOf(equResult);
                if (!Utilities.ValidateIntRange(result.EquValue, 0, 1024))
                {
                    result.Errors.add("InvalidDirectiveRange");
                }
                if (operatorCount > 3)
                {
                    result.Errors.add("InvalidExpressionOpCount");
                }
            }
            else if (result.Directive == Instruction.GetDirective("EXTRN"))
            {
                if (result.Label != null)
                {
                    result.Errors.add("InvalidNullLabel");
                }
                if (symTable.Rows.containsKey(result.Operands[0]))
                {
                    result.Errors.add("InvalidDuplicateLabel");
                }
                else if (!Utilities.ValidateLabel(result.Operands[0]))
                {
                    result.Errors.add("InvalidLabel");
                }
                else
                {
                    //System.out.println("Adding item " + result.Operands[0]);
                    symTable.Rows.put(result.Operands[0], new SymbolRow(result));
                }
            }
            else if (result.Directive == Instruction.GetDirective("RESET"))
            {
                try
                {
                    if (Integer.valueOf(result.Operands[0]) > lc)
                    {
                        lc = Integer.valueOf(result.Operands[0]);
                        result.LC = lc;
                    }
                    else
                    {
                        result.Errors.add("InvalidReset");
                    }
                }
                catch (NumberFormatException ex)
                {
                    if (symTable.Rows.containsKey(result.Operands[0]))
                    {
                        
                    }
                }
            }
            else if (result.Directive == Instruction.GetDirective("START"))
            {
                result.Operands[0] = result.Operands[0].split("[ \t]+")[0].trim();
                lc = Integer.parseInt(result.Operands[0]);


                if (lc < 0 || lc > 1023)
                {
                    result.Errors.add("InvalidStart");
                    fatalError = true;
                }

            }
            if (result.Directive.ConsumesMemory)
            {
                lc++;
            }
        } else
        {
            //error no operator
            result.Type = Instruction.Type.ERROR;
            if (result.Label != null)
            {
                if (symTable.Rows.containsKey(result.Label))
                {
                    symTable.Rows.remove(result.Label);
                }
            }
            result.Errors.add("InvalidOperation");
        }
    }

    private String CheckOperandError(String operand, int format)
    {
        String errorResult = null;

        if ((format & Instruction.Format.TEST) > 0)
        {
            boolean properFormat = false;
            for (int i=0; i < Instruction.TestPrefix.length; i++)
            {
                if (operand.startsWith(Instruction.TestPrefix[i]))
                {
                    operand = operand.substring(Instruction.TestPrefix[i].length());
                    properFormat = true;
                    break;
                }
            }
            if (properFormat)
            {
                format = Instruction.Format.NNNNNN & Instruction.Format.RLABEL;
            }
        }
        if ((format & Instruction.Format.NONE) > 0)
        {
            if (operand == null || "".equals(operand))
                return null;
            errorResult = "InvalidNullLabel";
        }
        if ((format & Instruction.Format.NNNNNN) > 0)
        {
            if (Utilities.ValidateIntRange(operand, -32768, 32767))
            {
                return null;
            }
            else
            {
                errorResult = "InvalidLiteral";
            }
        }
        if ((format & Instruction.Format.DAT) > 0)
        {
            if (Utilities.ValidateLiteral(operand))
            {
                return null;
            }
            else
            {
                errorResult = "InvalidDatVal";
            }
        }
        if ((format & Instruction.Format.LITERAL) > 0)
        {
            if (Utilities.ValidateLiteral(operand))
            {
                return null;
            }
            else
            {
                errorResult = "InvalidLiteral";
            }
        }
        if ((format & Instruction.Format.RLABEL) > 0)
        {
            //System.out.println("Checking rlabel");
            if (Utilities.ValidateLabel(operand) && operand != null)
            {
                return null;
            }
            else
            {
                //System.out.println("InvalidRLabel found");
                errorResult = "InvalidRLabel";
            }
        }
        if ((format & Instruction.Format.DUMP) > 0)
        {
            if (Utilities.ValidateIntRange(operand, 1, 3))
            {
                return null;
            }
            else
            {
                errorResult = "InvalidLiteral";
            }
        }
        if ((format & Instruction.Format.NMAX) > 0)
        {
            //System.out.println("Checking range for " + operand);
            if (Utilities.ValidateIntRange(operand, 0, 1023))
            {
                return null;
            }
            else if (operand.charAt(0) == '*')
            {
                if (operand.equals("*"))
                {
                    return null;
                }
                else if(operand.matches("\\*[\\+\\-]\\d+"))
                {
                    return null;
                }
                else if(operand.matches("\\*[\\+\\-].+"))
                {
                    //System.out.println("matches possible equ");
                    if (symTable.Rows.containsKey(operand.substring(2, operand.length())))
                    {
                        if (symTable.Rows.get(operand.substring(2, operand.length())).Data.EquValue != null)
                        {
                            //System.out.println("equ found!");
                            if (symTable.Rows.get(operand.substring(2, operand.length())).Data.EquValue.matches("\\d+"))
                            {
                                if (Utilities.ValidateIntRange(String.valueOf(lc + Utilities.ParseInteger(operand.charAt(1) + symTable.Rows.get(operand.substring(2, operand.length())).Data.EquValue)), 0, 1023))
                                {
                                    return null;
                                }
                            }
                        }
                    }
                }
            }
            else
            {

                //System.out.println("Found invalid nmax");
                errorResult = "InvalidSField";
            }
        }
        if ((format & Instruction.Format.NN) > 0)
        {
            if (Utilities.ValidateIntRange(operand, 0, 255))
            {
                return null;
            }
            else
            {
                errorResult = "InvalidLiteral";
            }
        }
        if ((format & Instruction.Format.MLABEL) > 0)
        {
            if (symTable.Rows.containsKey(operand))
            {
                return null;
            }
            else
            {
                errorResult = "InvalidSField";
            }
        }
        if ((format & Instruction.Format.EXPRESSION) > 0)
        {
            if (operand.matches("[\\*_\\w\\d]+((\\+|\\-)[\\w\\d_]+)*"))
            {
                return null;
            }
            else
            {
                errorResult = "InvalidExpression";
            }
        }

        return errorResult;
    }

    private int EvaluateEQUeLabel(String label)
    {
        int result = -1;
        try
        {
            //System.out.println("Parsing label '" + label + "'");
            result = Integer.parseInt(label);
            //System.out.println("Result is '" + result + "'");
            return result;
        }
        catch (NumberFormatException ex)
        {
            if (symTable.Rows.containsKey(label))
            {
                if (symTable.Rows.get(label).Data.EquValue != null)
                {
                    return EvaluateEQUeLabel(symTable.Rows.get(label).Data.EquValue);
                }
            }
        }
        //System.out.println("Returning result " + result);
        return result;
    }

    public void DisplayData()
    {
        for (IntermediateData d : Data)
        {
            d.PrintData();
            System.out.println();
        }
        symTable.PrintTable();
        //symTable.PrintTable();
    }
}
